import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

public class MyPoolSizeCalculator {
	
	private long testTime;
	private long epsilon;
	
	private volatile boolean expired;
	
	public MyPoolSizeCalculator(){}
	
	public MyPoolSizeCalculator(long testtime){
		testTime = testtime;
	}
	public MyPoolSizeCalculator(long testtime, long epsilon){
		testTime = testtime;
		this.epsilon = epsilon;
	}
	
	public int calculateWithoutTesttime(BigDecimal targetUtilization, Collection<Runnable> tasks){
		Iterator it = tasks.iterator();
		
		long computetime = 0;
		long executetime = 0;
		
		while(it.hasNext()){
			
			
			long start = System.currentTimeMillis();
			
			computetime = startWithoutTesttime((Runnable)it.next(), computetime);
			
			long end = System.currentTimeMillis();
			executetime = executetime +(end-start);
		}
		
		long waittime = executetime*1000000 - computetime ;
		return calculate(computetime, waittime, targetUtilization);
	}
	
	public int calculateWithTesttime(BigDecimal targetUtilization, Collection<Runnable> tasks){
		
	//	start(tasks);// warm up
		long cputime = getCurrentThreadCPUTime();
		start(tasks); // test intervall
		cputime = getCurrentThreadCPUTime() - cputime;
		long waittime = (testTime * 1000000) - cputime;
		
		return calculate(cputime, waittime, targetUtilization);
	}
	
	/**
	* Calculate by repeatedly run the task Based on Niklas Schlimm' work
	* (http://www.javacodegeeks.com/2012/04/threading-stories-about-robust-thread.html).
	*/
	
	public int calculateWithSigleTask(BigDecimal targetUtilization, Runnable task, int maxAttemps){
		
		start(task, maxAttemps); // warm up phase
		long cputime = getCurrentThreadCPUTime();
		start(task, maxAttemps); // test intervall	
		cputime = getCurrentThreadCPUTime() - cputime;
		long waittime = (testTime * 1000000) - cputime;
		return calculate(cputime, waittime, targetUtilization);
		
	}
	
	private int calculate(long cpu, long wait, BigDecimal targetUtilization) {

	    BigDecimal waitTime = new BigDecimal(wait);
	     
	    BigDecimal computeTime = new BigDecimal(cpu);
	     
	    BigDecimal numberOfCPU = new BigDecimal(Runtime.getRuntime().availableProcessors());
	     
	    BigDecimal optimalthreads = numberOfCPU.multiply(targetUtilization).multiply(
	    		 				new BigDecimal(1).add(waitTime.divide(computeTime, RoundingMode.HALF_UP)));
	    return optimalthreads.intValue();
	     
	//    System.out.println("* Optimal thread count: " + optimalthreadcount);
	 }
	
	/**
    * Runs the {@link Runnable} over a period defined in {@link #testtime}. Based on Heinz Kabbutz' ideas
    * (http://www.javaspecialists.eu/archive/Issue124.html). 
	*/
	
	private void start(Runnable task, int maxAttemps){
		
		long start = 0;
		int runs = 0;
		do {
			
			if (++runs > maxAttemps) {
				 throw new IllegalStateException("Test not accurate");
			}
			expired = false;
			start = System.currentTimeMillis();
			Timer timer = new Timer();
			timer.schedule(new TimerTask() {
				 public void run() { expired = true;} }, testTime);
 	        while (!expired) {
 	        	 task.run();
	        }
 	        start = System.currentTimeMillis() - start;
 	       
 	        timer.cancel();
		}while (Math.abs(start - testTime) > epsilon);
		 
		System.gc();// collect garbage
	}
	
	private void start(Collection<Runnable> tasks){
		
		long start = 0;
		int runs = 0;
		Iterator<Runnable> it = tasks.iterator();
	
		expired = false;
		start = System.currentTimeMillis();
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
				 public void run() { expired = true;} }, testTime);
 	    while (!expired && it.hasNext()) {
 	    	
 	        ((Runnable) it.next()).run(); 
	    }
 	         
 	    start = System.currentTimeMillis() - start;
 	    timer.cancel();
 	         
		System.gc();
	}
	
	private long startWithoutTesttime(Runnable task, long computeTime){
		
		long cputime1 = getCurrentThreadCPUTime();
		task.run();
		long cputime2 = getCurrentThreadCPUTime();
		computeTime = computeTime +(cputime2-cputime1);
		return computeTime;
	}
	protected long getCurrentThreadCPUTime(){
		return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
	}
}
